/**
 * 正则规则
 * 来源于vue/src/compiler/parser/html-parser.js
 */

//匹配格式：id="app"/id='app'/id=app
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
//匹配格式：标签名 div/span.../ <my-header>
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
//匹配格式：特殊的 标签格式 <my:header>
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
//匹配格式：<div
const startTagOpen = new RegExp(`^<${qnameCapture}`);
//匹配格式：> 或者是 />
const startTagClose = /^\s*(\/?)>/;
//匹配格式：</div>
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);

/**
 * 模板：
 * <div id="app" style="color: red; font-size: 20px;">
    hello {{name}}
    <h1>{{name}}</h1>
    <ul>
      <li style="color: green;">{{age}}</li>
      <li>{{info.job}}</li>
    </ul>
  </div> 

  如何匹配？
  1.先匹配 <div 
  2.删除 <div
  3.匹配 id="app"/id='app'/id=app
  4.删除 id="app"
  5.匹配 style="color: red; font-size: 20px;"
  6.将属性解析成对象存储 
    -> attrs: [{name:'style', value: {color: 'red', font-size: '20px'}}]
  7.删除 style="color: red; font-size: 20px;"
  8.继续匹配直到匹配到 > 说明结束
  9.删除 >
 */

//解析HTML为AST树
function parseHtmlToAST(html) {
  // console.log(html);

  let text,
    root,
    currentParent,
    stack = [];

  //当html存在时一直while直到截取完毕
  while (html) {
    //找到 < 
    let textEnd = html.indexOf('<');

    //说明是第一项
    if (textEnd === 0) {
      //做开始标签匹配
      const startTagMatch = parseStartTag();

      //当有match对象内容时
      if (startTagMatch) {
        //执行start()函数 返回tagName 和 attrs 信息 如div / [{...},{...}]
        start(startTagMatch.tagName, startTagMatch.attrs);
        //不走下面的程序继续走循环
        continue;
      }

      //匹配结束的标签
      const endTagMatch = html.match(endTag);

      //如果匹配到内容
      if (endTagMatch) {
        /**
         * console.log(endTagMatch);
         * [
         *   0: "</ul>",
         *   1: "ul",
         *   groups: undefined,
         *   index: 0,
         *   input: "</ul>\n  </div>",
         * length: 2
         * ]
         */
        advance(endTagMatch[0].length);
        //endTagMatch[1] 第一项 -> 
        end(endTagMatch[1]);
        continue;
      }
    }

    //证明
    if (textEnd > 0) {
      //从第0项截取到当前最近的位置
      text = html.substring(0, textEnd);
    }

    if (text) {
      //如果有文本就删除文本
      advance(text.length);
      chars(text);
    }
  }

  //组装AST树
  function createASTElement(tagName, attrs) {
    return {
      tag: tagName,
      //元素节点
      type: 1,
      children: [],
      attrs,
      //根据父节点才能拿出结构
      parent
    }
  }

  //对开始 处理的函数
  function start(tagName, attrs) {
    const element = createASTElement(tagName, attrs);
    /**
     * console.log(element);
     * {
     *   attrs: (2) [{…}, {…}],
     *   children: [],
     *   parent: Window,
     *   tag: "div",
     *   type: 1
     * }
     */

    //当AST树没有内容时，把新创建的AST结构赋值给AST树
    if (!root) {
      root = element;
    }

    //将div -> currentParent
    //currentParent作为临时的变量存储当前的标签 走到下一组标签时会擦去之前的标签
    currentParent = element;

    //将div放入stack里
    //stack[div]
    stack.push(element);

  }

  //对结束 处理的函数
  function end(tagName) {
    //删除stack数组里的最后一项
    //stack[div span] -> stack[div]
    //pop()返回值为被删除的那一项 span -> element = span
    const element = stack.pop();

    //currentParent -> div
    currentParent = stack[stack.length - 1];

    //说明存在父级
    if (currentParent) {
      //span.parent = div
      element.parent = currentParent;
      //div.children.push(span);
      currentParent.children.push(element);
    }
  }

  //处理字符串
  function chars(text) {
    //去除前后空格
    text = text.trim();

    //有值
    if (text.length > 0) {
      currentParent.children.push({
        //文本节点
        type: 3,
        text
      });
    }
  }

  //截取字符串函数(删除)
  function advance(n) {
    //substring()从第几位开始截取 直达最后
    html = html.substring(n);
  }

  //解析开始标签 
  function parseStartTag() {
    let end,
      attr;

    //返回标签的 match对象
    /**
     * {
     *   tag: '',
     *   attrs: ''
     * }
     */

    //首先找到start 
    //匹配格式：<div
    const start = html.match(startTagOpen);
    /**
     console.log(start);
     * [
     *   0: "<div",
     *   1: "div",
     *   groups: undefined,
     *   index: 0,
     *   input: "<div id=\"app\" style=\"color: red; font-size: 20px...
     *   length: 2
     * ]
     */

    //匹配到 <div
    if (start) {
      const match = {
        tagName: start[1],
        attrs: []
      }

      //删除 <div
      //从第0项 <div 的长度开始截取到最后
      //不要 <div -> 截取 -> id="app" style="color: red; font-size: 20px;">....
      // console.log(start[0].length); 4
      advance(start[0].length);
      /**
       * console.log(html);
       * id="app" style="color: red; font-size: 20px;">...</div>
       */

      //继续匹配 直到匹配到结束标签 > 或 </div> 
      //证明标签内部没有属性
      //没有匹配到结束标签并且匹配到属性
      while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
        /**
         * console.log(attr);
         * [
         *   0: " id=\"app\"",
         *   1: "id",
         *   2: "=",
         *   3: "app",
         *   4: undefined, 这里匹配到的是单引号的 'app' 即 id = 'app'
         *   5: undefined, 这里匹配到的是没有引号的 app 即 id = app
         *   groups: undefined,
         *   index: 0,
         *   input: " id=\"app\" style=\"color: red; font-size: 20px;\...,
         *   length: 6
         * ]
         */

        //匹配到属性时，存储属性到match对象里
        match.attrs.push({
          name: attr[1],
          //第3项找不到 找第4项 再找不到 找第5项
          value: attr[3] || attr[4] || artt[5]
        });

        // console.log(match);
        //{tagName: 'div', attrs: [{name: 'id', value: 'app'}]}

        //删除匹配完的属性
        //从第0项 " id=\"app\"" 的长度开始截取到最后
        // console.log(attr[0].length); 9
        advance(attr[0].length);
      }
      //如果匹配到结束标签了
      if (end) {
        /**
         * console.log(end);
         * [
         *   0: ">",
         *   1: "",
         *   groups: undefined,
         *   index: 0,
         *   input: ">\n    hello {{name}}\n    <h1>{{name}}</h1>\n    <...,
         *   length: 2
         * ]
         */
        advance(end[0].length);

        /**
         * console.log(match);
         * {
         *   tagName: "div",
         *   attrs: [
         *     {name: 'id', value: 'app'},
         *     {name: 'style', value: 'color: red; font-size: 20px;'}
         *   ]
         * }
         */

        return match;
      }
    }
  }

  /**
   * root -> 拼接好的AST出来了
   * console.log(root);
   * {
   *   tag: "div",
   *   type: 1,
   *   parent: Window,
   *   attrs: (2) [
   *     0: {name: 'id', value: 'app'},
   *     1: {name: 'style', value: 'color: red; font-size: 20px;}
   *   ],
   *   children: [
   *     0: {type: 3, text: 'hello {{name}}'},
   *     1: {
   *       attrs: [],
   *       children: [{type: 3, text: '{{name}}'}],
   *       parent: {
   *         tag: "div",
   *         type: 1,
   *         attrs: (2) [{name: 'id', value: 'app'}, {{name: 'style', value: 'color: red; font-siz...}],
   *         children: (3) [{…}, {…}, {…}],
   *         parent: Window
   *       },
   *       tag: "h1",
   *       type: 1
   *     },
   *     2: {tag: 'ul', type: 1, children: Array(2), attrs: Array...}
   *   ]
   * }
   */

  return root;

}

export {
  parseHtmlToAST
}